<template>
    <el-container>
        <el-header style="background: #fff; padding: 0; height: 50px">
            <div class="nav-api-header">
                <div class="record-api-header">
                    <div style="display: flex; margin-left: 10px">
                        <span class="uniform-text"
                        >录制接口前, 请先配置好app/web代理~<strong>（在线体验已关闭此功能）</strong>
                            <el-link
                                class="uniform-text"
                                type="primary"
                                :href="$store.state.recordCaseDocsURL"
                                target="_blank"
                                rel="noreferrer"
                                >参考文档</el-link
                            ></span
                        >
                    </div>
                    <div
                        style="display: flex; position: absolute; right: 30px; align-items: center; "
                    >
                        <el-switch
                            style="display: block; margin-right: 10px"
                            :disabled="recordStatus"
                            v-model="isLocalEndpoint"
                            active-color="#13ce66"
                            inactive-color="#ff4949"
                            active-text="录制本机"
                            inactive-text="录制其它端"
                        ></el-switch>
                        <el-form
                            class="inline-form"
                            :model="ipUrlRegexForm"
                            :rules="ipUrlRegexRules"
                            ref="ipUrlRegexForm"
                            status-icon
                        >
                            <el-form-item
                                style="margin-bottom: 0;"
                                prop="clientIP"
                                v-if="!isLocalEndpoint"
                            >
                                <el-input
                                    v-model.trim="ipUrlRegexForm.clientIP"
                                    size="small"
                                    style="width: 200px; margin-right: 10px"
                                    placeholder="请输入IP地址, 如127.0.0.1"
                                ></el-input>
                            </el-form-item>
                            <el-form-item
                                style="margin-bottom: 0;"
                                prop="urlRegex"
                            >
                                <el-input
                                    v-model.trim="ipUrlRegexForm.urlRegex"
                                    size="small"
                                    style="width: 360px; margin-right: 10px"
                                    placeholder="请输入要录制的接口地址, 如https://example.com/api"
                                ></el-input>
                            </el-form-item>
                        </el-form>
                        <el-button
                            type="primary"
                            size="small"
                            icon="el-icon-video-camera"
                            @click="startRecord"
                            v-if="!recordStatus"
                            >{{
                                recordCaseData.results.length === 0
                                    ? "开始录制"
                                    : "重新录制"
                            }}</el-button
                        >
                        <el-button
                            type="danger"
                            size="small"
                            icon="el-icon-video-camera"
                            @click="stopRecord()"
                            v-else-if="recordStatus"
                        >
                            停止录制
                        </el-button>
                        <el-button
                            type="primary"
                            size="small"
                            icon="el-icon-refresh"
                            @click="getRecordAPIList"
                            >获取录制接口</el-button
                        >
                        <el-button
                            style="margin-left: 10px"
                            type="success"
                            :disabled="!isSelectRecordAPI"
                            size="small"
                            icon="el-icon-mouse"
                            @click="caseDialogVisible = true"
                            >生成用例</el-button
                        >
                    </div>
                </div>
            </div>
            <el-dialog
                title="生成用例"
                width="30%"
                :visible.sync="caseDialogVisible"
                :before-close="handleBeforeClose"
                :close-on-click-modal="false"
                :style="{ 'text-align': 'center' }"
            >
                <el-form
                    :model="recordCaseForm"
                    :rules="recordCaseRules"
                    ref="recordCaseForm"
                    label-width="100px"
                >
                    <el-form-item label="用例名称" prop="caseName">
                        <el-input
                            v-model.trim="recordCaseForm.caseName"
                            clearable
                            placeholder="请输入用例名称"
                        ></el-input>
                    </el-form-item>

                    <el-form-item label="用例配置" prop="config">
                        <el-select
                            v-model="recordCaseForm.config"
                            value-key="id"
                            placeholder="请选择用例配置"
                            :style="{ width: '100%' }"
                        >
                            <el-option
                                v-for="item in configOptions"
                                :key="item.id"
                                :label="item.name"
                                :value="item.name"
                            ></el-option>
                        </el-select>
                    </el-form-item>

                    <el-form-item label="用例目录" prop="caseDir">
                        <el-select
                            v-model="selectedCaseNodeLabel"
                            value-key="id"
                            placeholder="请选择用例目录"
                            :style="{ width: '100%' }"
                            ref="selectCaseNode"
                        >
                            <el-option
                                :value="selectCaseOptionValue"
                                class="tree-select-option-item"
                            >
                                <el-tree
                                    :data="caseDataTree"
                                    :expand-on-click-node="false"
                                    :check-strictly="true"
                                    @node-click="handleCaseNodeClick"
                                    highlight-current
                                    node-key="id"
                                    check-on-click-node
                                    style="max-width: 100%;"
                                >
                                    <span
                                        class="custom-tree-node"
                                        slot-scope="{ node, data }"
                                        style="display: flex; flex-direction: row; min-width: 0;"
                                    >
                                        <span class="el-tree-span">
                                            <i
                                                v-if="
                                                    node.childNodes.length > 0
                                                "
                                                class="el-icon-folder-opened"
                                            ></i>
                                            <i v-else class="el-icon-folder"></i
                                            >&nbsp;&nbsp;{{ node.label }}
                                        </span>
                                    </span>
                                </el-tree>
                            </el-option>
                        </el-select>
                    </el-form-item>

                    <div
                        style="display: flex; align-items: center; justify-content: flex-start;"
                    >
                        <el-form-item
                            label="接口目录"
                            prop="apiDir"
                            v-if="isSyncAPI"
                            style="flex-grow: 1; margin-right: 10px; margin-bottom: 0;"
                        >
                            <el-select
                                v-model="selectedApiNodeLabel"
                                value-key="id"
                                placeholder="请选择接口目录"
                                style="width: 100%"
                                ref="selectApiNode"
                            >
                                <el-option
                                    :value="selectApiOptionValue"
                                    class="tree-select-option-item"
                                >
                                    <el-tree
                                        :data="apiDataTree"
                                        :expand-on-click-node="false"
                                        :check-strictly="true"
                                        @node-click="handleApiNodeClick"
                                        highlight-current
                                        node-key="id"
                                        check-on-click-node
                                        style="max-width: 100%;"
                                    >
                                        <span
                                            class="custom-tree-node"
                                            slot-scope="{ node, data }"
                                            style="display: flex; flex-direction: row; min-width: 0;"
                                        >
                                            <span class="el-tree-span">
                                                <i
                                                    v-if="
                                                        node.childNodes.length >
                                                            0
                                                    "
                                                    class="el-icon-folder-opened"
                                                ></i>
                                                <i
                                                    v-else
                                                    class="el-icon-folder"
                                                ></i
                                                >&nbsp;&nbsp;{{ node.label }}
                                            </span>
                                        </span>
                                    </el-tree>
                                </el-option>
                            </el-select>
                        </el-form-item>

                        <el-switch
                            v-model="isSyncAPI"
                            active-color="#13ce66"
                            inactive-color="#ff4949"
                            active-text="同步API"
                            style="margin-left: 20px; margin-bottom: 0;"
                        ></el-switch>
                    </div>
                </el-form>
                <span
                    slot="footer"
                    class="dialog-footer"
                    style="display: flex; justify-content: flex-end;"
                >
                    <el-button @click="resetForm('recordCaseForm')"
                        >取 消</el-button
                    >
                    <el-button
                        type="primary"
                        @click="handleConfirm('recordCaseForm')"
                        >确 定</el-button
                    >
                </span>
            </el-dialog>
        </el-header>

        <el-container>
            <el-container>
                <el-main class="el-main-table">
                    <div class="record-table">
                        <el-table
                            highlight-current-row
                            stripe
                            :data="currentTableData"
                            v-loading="loading"
                            height="calc(100%)"
                            @cell-mouse-enter="cellMouseEnter"
                            @cell-mouse-leave="cellMouseLeave"
                            @selection-change="handleSelectionChange"
                            :row-key="getRowKeys"
                        >
                            <el-table-column
                                :reserve-selection="true"
                                type="selection"
                                width="55"
                            ></el-table-column>

                            <el-table-column label="编号" width="80">
                                <template slot-scope="scope">
                                    {{
                                        (currentPage - 1) * pageSize +
                                            scope.$index +
                                            1
                                    }}
                                </template>
                            </el-table-column>
                            <el-table-column label="请求地址" width="670">
                                <template slot-scope="scope">
                                    <el-tooltip
                                        :content="scope.row.url"
                                        placement="top"
                                    >
                                        <a
                                            :href="scope.row.url"
                                            class="a-record a-url"
                                            target="_blank"
                                            >{{ scope.row.url }}</a
                                        >
                                    </el-tooltip>
                                </template>
                            </el-table-column>
                            <el-table-column label="请求方式" width="150">
                                <template slot-scope="scope">
                                    <el-tag
                                        :type="
                                            getTagType(scope.row.request_method)
                                        "
                                        size="mini"
                                        >{{ scope.row.request_method }}</el-tag
                                    >
                                </template>
                            </el-table-column>
                            <el-table-column label="请求Headers" width="150">
                                <template slot-scope="scope">
                                    <a
                                        href="#"
                                        class="a-record"
                                        @click.prevent="
                                            showDetails(
                                                scope.row.request_headers,
                                                '请求Headers'
                                            )
                                        "
                                        >详情</a
                                    >
                                </template>
                            </el-table-column>
                            <el-table-column label="请求Body" width="150">
                                <template slot-scope="scope">
                                    <a
                                        v-if="scope.row.body"
                                        href="#"
                                        class="a-record"
                                        @click.prevent="
                                            showDetails(
                                                scope.row.body,
                                                '请求Body'
                                            )
                                        "
                                        >详情</a
                                    >
                                    <span v-else>-</span>
                                </template>
                            </el-table-column>
                            <el-table-column label="响应Headers" width="150">
                                <template slot-scope="scope">
                                    <a
                                        href="#"
                                        class="a-record"
                                        @click.prevent="
                                            showDetails(
                                                scope.row.response_headers,
                                                '响应Headers'
                                            )
                                        "
                                        >详情</a
                                    >
                                </template>
                            </el-table-column>
                            <el-table-column label="Response" width="150">
                                <template slot-scope="scope">
                                    <a
                                        href="#"
                                        class="a-record"
                                        @click.prevent="
                                            showDetails(
                                                scope.row.response_content,
                                                'Response'
                                            )
                                        "
                                        >详情</a
                                    >
                                </template>
                            </el-table-column>
                            <el-table-column label="操作">
                                <template slot-scope="scope">
                                    <el-row v-show="currentRow === scope.row">
                                        <el-button
                                            v-show="recordCaseData.count !== 0"
                                            type="danger"
                                            icon="el-icon-delete"
                                            title="删除"
                                            circle
                                            size="mini"
                                            @click="
                                                handleRemoveRecord(scope.$index)
                                            "
                                        ></el-button>
                                    </el-row>
                                </template>
                            </el-table-column>
                        </el-table>
                        <div class="pagination-container">
                            <el-pagination
                                background
                                v-show="recordCaseData.results.length !== 0"
                                @size-change="handleSizeChange"
                                @current-change="handleCurrentChange"
                                :current-page.sync="currentPage"
                                :page-sizes="[10, 20, 30, 40]"
                                :page-size="pageSize"
                                :pager-count="5"
                                layout="total, sizes, prev, pager, next"
                                :total="recordCaseData.results.length"
                            ></el-pagination>
                        </div>
                    </div>
                    <el-dialog
                        :visible.sync="recordDialogVisible"
                        :title="dialogTitle"
                        :close-on-click-modal="false"
                        width="50%"
                    >
                        <pre
                            v-html="highlightedCode"
                            class="dialog-code-block"
                        ></pre>
                        <span slot="footer" class="dialog-footer">
                            <el-button
                                type="primary"
                                @click="recordDialogVisible = false"
                                >知道了</el-button
                            >
                        </span>
                    </el-dialog>
                </el-main>
            </el-container>
        </el-container>
    </el-container>
</template>

<script>
import hljs from "highlight.js";
import "highlight.js/styles/vs2015.css";

export default {
    name: "RecordCase",
    components: {},
    data() {
        return {
            isSyncAPI: false,
            isLocalEndpoint: true,
            recordDialogVisible: false,
            caseDialogVisible: false,
            dialogTitle: "",
            highlightedCode: "",
            loading: true,
            isSelectRecordAPI: false,
            currentRow: "",
            pageSize: 10,
            currentPage: 1,
            configOptions: [],
            selectRecordAPI: [],
            selectedCaseNodeLabel: "",
            selectedApiNodeLabel: "",
            selectCaseOptionValue: undefined,
            selectApiOptionValue: undefined,
            caseDataTree: [],
            apiDataTree: [],
            recordStatus: false,
            recordCaseData: {
                results: [],
                regex: "",
                status: false
            },
            ipUrlRegexForm: {
                clientIP: "",
                urlRegex: ""
            },
            ipUrlRegexRules: {
                clientIP: [
                    {
                        required: true,
                        message: "请输入IP地址",
                        trigger: "blur"
                    },
                    {
                        pattern: /^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/,
                        message: "请输入有效的IP地址",
                        trigger: "blur"
                    }
                ],
                urlRegex: [
                    {
                        required: true,
                        message: "请输入要录制的接口地址",
                        trigger: "blur"
                    }
                ]
            },
            recordCaseForm: {
                config: "",
                caseDir: "",
                apiDir: "",
                caseName: ""
            },
            recordCaseRules: {
                config: [
                    {
                        required: true,
                        message: "请选择配置",
                        trigger: "change"
                    }
                ],
                caseDir: [
                    {
                        required: true,
                        message: "请选择用例目录",
                        trigger: "change"
                    }
                ],
                caseName: [
                    {
                        required: true,
                        message: "请输入用例名称",
                        trigger: "blur"
                    },
                    {
                        min: 0,
                        max: 50,
                        message: "最多不超过50个字符",
                        trigger: "blur"
                    }
                ]
            }
        };
    },
    watch: {
        isSyncAPI(newValue) {
            if (newValue) {
                this.recordCaseRules.apiDir = [
                    {
                        required: true,
                        message: "请选择接口目录",
                        trigger: "change"
                    }
                ];
            } else {
                delete this.recordCaseRules.apiDir;
            }
        }
    },
    computed: {
        currentTableData() {
            const startIndex = (this.currentPage - 1) * this.pageSize;
            const endIndex = startIndex + this.pageSize;
            return this.recordCaseData.results.slice(startIndex, endIndex);
        },
        getTagType() {
            return requestMethod => {
                switch (requestMethod) {
                    case "POST":
                        return "success";
                    case "GET":
                        return "";
                    case "PUT":
                        return "warning";
                    case "DELETE":
                        return "danger";
                    default:
                        return "info";
                }
            };
        }
    },
    methods: {
        computedIndex(index) {
            return (this.currentPage - 1) * this.pageSize + index + 1;
        },
        getRowKeys(row) {
            return row.id;
        },
        showDetails(data, title) {
            this.dialogTitle = title;

            let jsonData;
            if (typeof data === "string") {
                try {
                    jsonData = JSON.parse(data);
                } catch (e) {
                    jsonData = data;
                }
            } else {
                jsonData = data;
            }

            this.highlightedCode = hljs.highlight(
                "json",
                JSON.stringify(jsonData, null, 4)
            ).value;
            this.recordDialogVisible = true;
        },

        getCurrentIP() {
            return this.isLocalEndpoint ? "" : this.ipUrlRegexForm.clientIP;
        },
        startRecord() {
            this.$refs.ipUrlRegexForm.validate(valid => {
                if (valid) {
                    const ip = this.getCurrentIP();
                    const regex = this.ipUrlRegexForm.urlRegex;
                    const isLocalEndpoint = this.isLocalEndpoint;
                    this.tryToStartRecord(ip, regex, isLocalEndpoint);
                } else {
                    return false;
                }
            });
        },
        tryToStartRecord(ip, regex, isLocalEndpoint) {
            this.$api
                .recordStart({
                    params: {
                        ip: ip,
                        regex: regex,
                        local: isLocalEndpoint
                    }
                })
                .then(resp => {
                    if (resp.success) {
                        this.recordStatus = true;
                        this.$message.success(resp.msg);
                    } else if (resp.code === "0005") {
                        // 当前IP已经在录制中，提示用户
                        this.$confirm("此IP正在录制中, 是否重新录制?", "提示", {
                            confirmButtonText: "确定",
                            cancelButtonText: "取消",
                            type: "warning"
                        })
                            .then(() => {
                                // 用户确认重新录制
                                this.stopRecord(ip, false).then(() => {
                                    // 停止成功后，重新开始录制
                                    this.tryToStartRecord(ip, regex);
                                });
                            })
                            .catch(() => {
                                // 用户取消操作
                            });
                    } else {
                        // 其他错误，恢复开始录制按钮
                        this.recordStatus = false;
                        this.$message.error(resp.msg);
                    }
                });
        },
        stopRecord(ip, showSuccessMessage = true) {
            if (!ip) {
                ip = this.getCurrentIP();
            }
            return new Promise((resolve, reject) => {
                this.$api
                    .recordStop({
                        params: { ip: ip }
                    })
                    .then(resp => {
                        if (resp.success) {
                            this.recordStatus = false;
                            if (showSuccessMessage) {
                                this.$message.success(resp.msg);
                            }
                            resolve();
                        } else {
                            this.$message.error(resp.msg);
                            reject();
                        }
                    });
            });
        },
        handleSelectionChange(val) {
            // val 是当前选中的行数组，但顺序是用户选择的顺序
            // this.currentTableData 是当前页面表格的数据，按照表格的顺序
            // 使用 filter 方法，根据 currentTableData 的顺序重新构建选中项数组
            this.selectRecordAPI = this.currentTableData.filter(row =>
                val.includes(row)
            );
            // 更新是否已经选择录制API, 依赖这个属性来判断是否禁用生成用例按钮
            this.isSelectRecordAPI = this.selectRecordAPI.length > 0;
        },
        cellMouseEnter(row) {
            this.currentRow = row;
        },
        cellMouseLeave() {
            this.currentRow = "";
        },
        handleRemoveRecord(index) {
            const ip = this.isLocalEndpoint ? "" : this.ipUrlRegexForm.clientIP;
            const globalIndex = this.computedIndex(index) - 1;
            this.$confirm("删除录制接口，是否继续?", "提示", {
                confirmButtonText: "确定",
                cancelButtonText: "取消",
                type: "warning"
            }).then(() => {
                this.$api
                    .recordRemove({
                        params: { index: globalIndex, ip: ip }
                    })
                    .then(resp => {
                        if (resp.success) {
                            this.$message.success(resp.msg);
                            this.getRecordAPIList();
                        } else {
                            this.$message.error(resp.msg);
                        }
                    });
            });
        },
        handleSizeChange(newSize) {
            this.pageSize = newSize;
            this.currentPage = 1;
        },
        handleCurrentChange(newPage) {
            this.currentPage = newPage;
        },
        getRecordAPIList() {
            const ip = this.isLocalEndpoint ? "" : this.ipUrlRegexForm.clientIP;
            this.$api
                .getRecordStatus({
                    params: {
                        ip: ip
                    }
                })
                .then(resp => {
                    this.recordCaseData = {
                        // 使用展开运算符复制resp对象中的所有属性到recordCaseData中
                        ...resp,
                        // 使用map函数处理resp.results数组，为每个元素添加一个唯一标识符id
                        results: resp.results.map((item, index) => ({
                            // 使用展开运算符复制当前元素（item）的所有属性
                            ...item,
                            // 使用数组的索引index作为新属性id的值，提供一个唯一标识符
                            id: index
                        }))
                    };
                    // 正在录制时，更新url和录制状态
                    this.ipUrlRegexForm.urlRegex = this.recordCaseData.regex;
                    if (!this.recordCaseData.local) {
                        this.ipUrlRegexForm.clientIP = this.recordCaseData.ip;
                    }
                    this.isLocalEndpoint = this.recordCaseData.local;
                    this.recordStatus = this.recordCaseData.status;
                    this.loading = false;
                });
        },
        handleConfirm(formName) {
            this.$refs[formName].validate(valid => {
                if (valid) {
                    const caseConfig = {
                        body: {
                            name: this.recordCaseForm.config,
                            method: "config"
                        }
                    };
                    this.$api
                        .generateTestCase({
                            name: this.recordCaseForm.caseName,
                            length: this.selectRecordAPI.length,
                            project: this.$route.params.id,
                            case_dir: this.recordCaseForm.caseDir,
                            api_dir: this.recordCaseForm.apiDir,
                            config: caseConfig,
                            requests: this.selectRecordAPI
                        })
                        .then(resp => {
                            if (resp.success) {
                                this.$message.success(resp.msg);
                            } else {
                                this.$message.error(resp.msg);
                            }
                            this.resetForm(formName);
                        });
                }
            });
        },
        handleBeforeClose(done) {
            this.resetForm("recordCaseForm");
            done();
        },
        resetForm(formName) {
            this.$refs[formName].resetFields();
            this.caseDialogVisible = false;
            this.isSyncAPI = false;
            this.selectedApiNodeLabel = "";
            this.selectedCaseNodeLabel = "";
        },
        getConfig() {
            this.$api.getAllConfig(this.$route.params.id).then(resp => {
                this.configOptions = resp;
                const _config = this.configOptions.filter(
                    item => item.is_default === true
                );
                if (_config.length) {
                    this.recordCaseForm.config = _config[0].name;
                }
            });
        },
        getCaseTree() {
            this.$api
                .getTree(this.$route.params.id, { params: { type: 2 } })
                .then(resp => {
                    this.caseDataTree = resp.tree;
                });
        },
        getApiTree() {
            this.$api
                .getTree(this.$route.params.id, { params: { type: 1 } })
                .then(resp => {
                    this.apiDataTree = resp.tree;
                });
        },
        handleCaseNodeClick(node) {
            this.recordCaseForm.caseDir = node.id;
            this.selectedCaseNodeLabel = node.label;
            this.$refs.selectCaseNode.toggleMenu();
        },
        handleApiNodeClick(node) {
            this.recordCaseForm.apiDir = node.id;
            this.selectedApiNodeLabel = node.label;
            this.$refs.selectApiNode.toggleMenu();
        }
    },
    mounted() {
        this.getConfig();
        this.getCaseTree();
        this.getApiTree();
        this.getRecordAPIList();
    }
};
</script>

<style scoped>
.tree-select-option-item {
    background: #fff;
    overflow: scroll;
    height: 200px;
    overflow-x: hidden;
}

.dialog-code-block {
    background-color: black;
    padding: 16px;
    max-width: 100%;
    overflow-x: auto;
    white-space: pre-wrap; /* 保持换行符 */
    word-wrap: break-word; /* 在单词边界处断行 */
}
.a-record {
    color: #409eff;
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.record-table {
    position: fixed;
    bottom: 0;
    right: 0;
    left: 220px;
    top: 100px;
    margin-left: -10px;
    padding-bottom: 60px;
}

.el-main-table {
    padding: 0;
    margin-left: 10px;
    margin-top: 10px;
}

.record-api-header {
    display: flex;
    height: 50px;
    justify-content: flex-start;
    align-items: center;
}

.el-tree-span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 0 1 auto;
}

.uniform-text {
    font-size: 14px;
    line-height: 1.5;
}

.inline-form {
    display: flex;
    justify-content: flex-start;
    align-items: flex-end;
}
</style>
